/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


// r_batch.cpp -- back-end geometry batching


#include "r_local.h"


/*
 ==================
 RB_CheckOverflow
 ==================
*/
void RB_CheckOverflow (int numIndices, int numVertices, int maxIndices, int maxVertices){

	if (backEnd.numIndices + numIndices <= maxIndices && backEnd.numVertices + numVertices <= maxVertices)
		return;

	RB_EndBatch();

	if (numIndices > maxIndices)
		Com_Error(false, "RB_CheckOverflow: indices > MAX (%i > %i)", numIndices, maxIndices);
	if (numVertices > maxVertices)
		Com_Error(false, "RB_CheckOverflow: vertices > MAX (%i > %i)", numVertices, maxVertices);

	RB_BeginBatch(backEnd.material, backEnd.entity, backEnd.shadowVolume, backEnd.shadowCaps);
}

/*
 ==================
 RB_BeginBatch
 ==================
*/
void RB_BeginBatch (material_t *material, entity_t *entity, bool shadowVolume, bool shadowCaps){

	// Set the batch state
	backEnd.material = material;
	backEnd.entity = entity;

	backEnd.shadowVolume = shadowVolume;
	backEnd.shadowCaps = shadowCaps;

	backEnd.vertexBuffer = NULL;
	backEnd.vertexPointer = NULL;
}

/*
 ==================
 RB_EndBatch
 ==================
*/
void RB_EndBatch (void){

	if (!backEnd.numIndices || !backEnd.numVertices)
		return;

	// Bind the vertex buffer
	if (backEnd.vertexBuffer)
		GL_BindVertexBuffer(backEnd.vertexBuffer);
	else {
		GL_BindVertexBuffer(NULL);

		if (!backEnd.shadowVolume)
			backEnd.vertexPointer = backEnd.vertices;
		else
			backEnd.vertexPointer = backEnd.shadowVertices;
	}

	// Render the batch
	backEnd.renderBatch();

	// Check for errors
	if (!r_ignoreGLErrors->integerValue)
		GL_CheckForErrors();

	// Clear the arrays
	backEnd.numIndices = 0;
	backEnd.numVertices = 0;
}


/*
 ==============================================================================

 STANDARD RENDERING

 ==============================================================================
*/


/*
 ==================
 RB_BatchSurface
 ==================
*/
static void RB_BatchSurface (mesh_t *mesh){

	surface_t	*surface = (surface_t *)mesh;
	glIndex_t	*index;
	glVertex_t	*vertex;
	glIndex_t	*indices;
	glVertex_t	*vertices;
	int			i;

	// Check for overflow
	RB_CheckOverflow(surface->numIndices, surface->numVertices, MAX_INDICES, MAX_VERTICES);

	// Check for fast path
	if (!backEnd.debugTools && surface->ambientBuffer){
		// Set up vertex buffer
		backEnd.vertexBuffer = surface->ambientBuffer;

		// Set up indices
		indices = backEnd.indices + backEnd.numIndices;

		for (i = 0, index = surface->indices; i < surface->numIndices; i += 3, index += 3){
			indices[i+0] = surface->ambientIndex + index[0];
			indices[i+1] = surface->ambientIndex + index[1];
			indices[i+2] = surface->ambientIndex + index[2];
		}

		backEnd.numIndices += surface->numIndices;
		backEnd.numVertices += surface->numVertices;

		return;
	}

	// Set up indices
	indices = backEnd.indices + backEnd.numIndices;

	for (i = 0, index = surface->indices; i < surface->numIndices; i += 3, index += 3){
		indices[i+0] = backEnd.numVertices + index[0];
		indices[i+1] = backEnd.numVertices + index[1];
		indices[i+2] = backEnd.numVertices + index[2];
	}

	backEnd.numIndices += surface->numIndices;

	// Set up vertices
	vertices = backEnd.vertices + backEnd.numVertices;

	for (i = 0, vertex = surface->vertices; i < surface->numVertices; i++, vertex++){
		vertices[i].xyz[0] = vertex->xyz[0];
		vertices[i].xyz[1] = vertex->xyz[1];
		vertices[i].xyz[2] = vertex->xyz[2];
		vertices[i].normal[0] = vertex->normal[0];
		vertices[i].normal[1] = vertex->normal[1];
		vertices[i].normal[2] = vertex->normal[2];
		vertices[i].tangents[0][0] = vertex->tangents[0][0];
		vertices[i].tangents[0][1] = vertex->tangents[0][1];
		vertices[i].tangents[0][2] = vertex->tangents[0][2];
		vertices[i].tangents[1][0] = vertex->tangents[1][0];
		vertices[i].tangents[1][1] = vertex->tangents[1][1];
		vertices[i].tangents[1][2] = vertex->tangents[1][2];
		vertices[i].st[0] = vertex->st[0];
		vertices[i].st[1] = vertex->st[1];
		vertices[i].color[0] = vertex->color[0];
		vertices[i].color[1] = vertex->color[1];
		vertices[i].color[2] = vertex->color[2];
		vertices[i].color[3] = vertex->color[3];
	}

	backEnd.numVertices += surface->numVertices;
}

/*
 ==================
 RB_BatchModel
 ==================
*/
static void RB_BatchModel (mesh_t *mesh){

	mdlSurface_t	*surface = (mdlSurface_t *)mesh;
	glIndex_t		*index;
	glVertex_t		*vertex;
	glIndex_t		*indices;
	glVertex_t		*vertices;
	int				i;

	// Check for overflow
	RB_CheckOverflow(surface->numIndices, surface->numVertices, MAX_INDICES, MAX_VERTICES);

	// Check for fast path
	if (!backEnd.debugTools && surface->ambientBuffer){
		// Set up vertex buffer
		backEnd.vertexBuffer = surface->ambientBuffer;

		// Set up indices
		indices = backEnd.indices + backEnd.numIndices;

		for (i = 0, index = surface->indices; i < surface->numIndices; i += 3, index += 3){
			indices[i+0] = surface->ambientIndex + index[0];
			indices[i+1] = surface->ambientIndex + index[1];
			indices[i+2] = surface->ambientIndex + index[2];
		}

		backEnd.numIndices += surface->numIndices;
		backEnd.numVertices += surface->numVertices;

		return;
	}

	// Set up indices
	indices = backEnd.indices + backEnd.numIndices;

	for (i = 0, index = surface->indices; i < surface->numIndices; i += 3, index += 3){
		indices[i+0] = backEnd.numVertices + index[0];
		indices[i+1] = backEnd.numVertices + index[1];
		indices[i+2] = backEnd.numVertices + index[2];
	}

	backEnd.numIndices += surface->numIndices;

	// Set up vertices
	vertices = backEnd.vertices + backEnd.numVertices;

	for (i = 0, vertex = surface->vertices; i < surface->numVertices; i++, vertex++){
		vertices[i].xyz[0] = vertex->xyz[0];
		vertices[i].xyz[1] = vertex->xyz[1];
		vertices[i].xyz[2] = vertex->xyz[2];
		vertices[i].normal[0] = vertex->normal[0];
		vertices[i].normal[1] = vertex->normal[1];
		vertices[i].normal[2] = vertex->normal[2];
		vertices[i].tangents[0][0] = vertex->tangents[0][0];
		vertices[i].tangents[0][1] = vertex->tangents[0][1];
		vertices[i].tangents[0][2] = vertex->tangents[0][2];
		vertices[i].tangents[1][0] = vertex->tangents[1][0];
		vertices[i].tangents[1][1] = vertex->tangents[1][1];
		vertices[i].tangents[1][2] = vertex->tangents[1][2];
		vertices[i].st[0] = vertex->st[0];
		vertices[i].st[1] = vertex->st[1];
		vertices[i].color[0] = vertex->color[0];
		vertices[i].color[1] = vertex->color[1];
		vertices[i].color[2] = vertex->color[2];
		vertices[i].color[3] = vertex->color[3];
	}

	backEnd.numVertices += surface->numVertices;
}

/*
 ==================
 RB_BatchFoliage
 ==================
*/
static void RB_BatchFoliage (mesh_t *mesh){

	foliageInstance_t	*foliageInstance = (foliageInstance_t *)mesh;
	surface_t			*surface = foliageInstance->parentSurface;
	glIndex_t			*index;
	glVertex_t			*vertex;
	glIndex_t			*indices;
	glVertex_t			*vertices;
	float				dist, scale;
	int					i;

	tr.pc.dynamicFoliages++;
	tr.pc.dynamicIndices += surface->numIndices;
	tr.pc.dynamicVertices += surface->numVertices;

	// Compute alpha scale
	if (surface->foliageMaxDistance <= surface->foliageMinDistance)
		scale = 1.0f;
	else {
		dist = foliageInstance->origin.DistanceFast(backEnd.localParms.viewOrigin) / backEnd.viewParms.projectionMatrix[0][0];
		dist = M_ClampFloat(dist, surface->foliageMinDistance, surface->foliageMaxDistance);

		scale = 1.0f - ((dist - surface->foliageMinDistance) / (surface->foliageMaxDistance - surface->foliageMinDistance));
	}

	// Check for overflow
	RB_CheckOverflow(surface->numIndices, surface->numVertices, MAX_INDICES, MAX_VERTICES);

	// Set up indices
	indices = backEnd.indices + backEnd.numIndices;

	for (i = 0, index = surface->indices; i < surface->numIndices; i += 3, index += 3){
		indices[i+0] = backEnd.numVertices + index[0];
		indices[i+1] = backEnd.numVertices + index[1];
		indices[i+2] = backEnd.numVertices + index[2];
	}

	backEnd.numIndices += surface->numIndices;

	// Set up vertices
	vertices = backEnd.vertices + backEnd.numVertices;

	for (i = 0, vertex = surface->vertices; i < surface->numVertices; i++, vertex++){
		vertices[i].xyz[0] = vertex->xyz.Dot(foliageInstance->axis[0]) + foliageInstance->origin[0];
		vertices[i].xyz[1] = vertex->xyz.Dot(foliageInstance->axis[1]) + foliageInstance->origin[1];
		vertices[i].xyz[2] = vertex->xyz.Dot(foliageInstance->axis[2]) + foliageInstance->origin[2];
		vertices[i].normal[0] = vertex->normal.Dot(foliageInstance->axis[0]);
		vertices[i].normal[1] = vertex->normal.Dot(foliageInstance->axis[1]);
		vertices[i].normal[2] = vertex->normal.Dot(foliageInstance->axis[2]);
		vertices[i].tangents[0][0] = vertex->tangents[0].Dot(foliageInstance->axis[0]);
		vertices[i].tangents[0][1] = vertex->tangents[0].Dot(foliageInstance->axis[1]);
		vertices[i].tangents[0][2] = vertex->tangents[0].Dot(foliageInstance->axis[2]);
		vertices[i].tangents[1][0] = vertex->tangents[1].Dot(foliageInstance->axis[0]);
		vertices[i].tangents[1][1] = vertex->tangents[1].Dot(foliageInstance->axis[1]);
		vertices[i].tangents[1][2] = vertex->tangents[1].Dot(foliageInstance->axis[2]);
		vertices[i].st[0] = vertex->st[0];
		vertices[i].st[1] = vertex->st[1];
		vertices[i].color[0] = vertex->color[0];
		vertices[i].color[1] = vertex->color[1];
		vertices[i].color[2] = vertex->color[2];
		vertices[i].color[3] = M_FtolFast(vertex->color[3] * scale);
	}

	backEnd.numVertices += surface->numVertices;
}

/*
 ==================
 RB_BatchSprite
 ==================
*/
static void RB_BatchSprite (mesh_t *mesh){

	glIndex_t	*indices;
	glVertex_t	*vertices;
	odMat3		axis;
	float		s, c;
	int			i;

	tr.pc.dynamicSprites++;
	tr.pc.dynamicIndices += 6;
	tr.pc.dynamicVertices += 4;

	// Check for overflow
	RB_CheckOverflow(6, 4, MAX_INDICES, MAX_VERTICES);

	// Compute axes
	if (backEnd.entity->e.spriteOriented){
		axis[0] = backEnd.entity->e.axis[0];

		if (backEnd.entity->e.spriteRotation){
			M_SinCos(DEG2RAD(backEnd.entity->e.spriteRotation), &s, &c);

			s *= backEnd.entity->e.spriteRadius;
			c *= backEnd.entity->e.spriteRadius;

			axis[1] = backEnd.entity->e.axis[1] * c - backEnd.entity->e.axis[2] * s;
			axis[2] = backEnd.entity->e.axis[2] * c + backEnd.entity->e.axis[1] * s;
		}
		else {
			axis[1] = backEnd.entity->e.axis[1] * backEnd.entity->e.spriteRadius;
			axis[2] = backEnd.entity->e.axis[2] * backEnd.entity->e.spriteRadius;
		}
	}
	else {
		axis[0] = -backEnd.viewParms.axis[0];

		if (backEnd.entity->e.spriteRotation){
			M_SinCos(DEG2RAD(backEnd.entity->e.spriteRotation), &s, &c);

			s *= backEnd.entity->e.spriteRadius;
			c *= backEnd.entity->e.spriteRadius;

			axis[1] = backEnd.viewParms.axis[1] * c - backEnd.viewParms.axis[2] * s;
			axis[2] = backEnd.viewParms.axis[2] * c + backEnd.viewParms.axis[1] * s;
		}
		else {
			axis[1] = backEnd.viewParms.axis[1] * backEnd.entity->e.spriteRadius;
			axis[2] = backEnd.viewParms.axis[2] * backEnd.entity->e.spriteRadius;
		}
	}

	// Set up indices
	indices = backEnd.indices + backEnd.numIndices;

	for (i = 0; i < 2; i++){
		indices[i*3+0] = backEnd.numVertices;
		indices[i*3+1] = backEnd.numVertices + i + 1;
		indices[i*3+2] = backEnd.numVertices + i + 2;
	}

	backEnd.numIndices += 6;

	// Set up vertices
	vertices = backEnd.vertices + backEnd.numVertices;

	vertices[0].xyz[0] = backEnd.entity->e.origin[0] + axis[1][0] + axis[2][0];
	vertices[0].xyz[1] = backEnd.entity->e.origin[1] + axis[1][1] + axis[2][1];
	vertices[0].xyz[2] = backEnd.entity->e.origin[2] + axis[1][2] + axis[2][2];
	vertices[1].xyz[0] = backEnd.entity->e.origin[0] - axis[1][0] + axis[2][0];
	vertices[1].xyz[1] = backEnd.entity->e.origin[1] - axis[1][1] + axis[2][1];
	vertices[1].xyz[2] = backEnd.entity->e.origin[2] - axis[1][2] + axis[2][2];
	vertices[2].xyz[0] = backEnd.entity->e.origin[0] - axis[1][0] - axis[2][0];
	vertices[2].xyz[1] = backEnd.entity->e.origin[1] - axis[1][1] - axis[2][1];
	vertices[2].xyz[2] = backEnd.entity->e.origin[2] - axis[1][2] - axis[2][2];
	vertices[3].xyz[0] = backEnd.entity->e.origin[0] + axis[1][0] - axis[2][0];
	vertices[3].xyz[1] = backEnd.entity->e.origin[1] + axis[1][1] - axis[2][1];
	vertices[3].xyz[2] = backEnd.entity->e.origin[2] + axis[1][2] - axis[2][2];

	vertices[0].st[0] = 0.0f;
	vertices[0].st[1] = 0.0f;
	vertices[1].st[0] = 1.0f;
	vertices[1].st[1] = 0.0f;
	vertices[2].st[0] = 1.0f;
	vertices[2].st[1] = 1.0f;
	vertices[3].st[0] = 0.0f;
	vertices[3].st[1] = 1.0f;

	for (i = 0; i < 4; i++){
		vertices[i].normal[0] = axis[0][0];
		vertices[i].normal[1] = axis[0][1];
		vertices[i].normal[2] = axis[0][2];
		vertices[i].color[0] = 255;
		vertices[i].color[1] = 255;
		vertices[i].color[2] = 255;
		vertices[i].color[3] = 255;
	}

	backEnd.numVertices += 4;
}

/*
 ==================
 RB_BatchBeam
 ==================
*/
static void RB_BatchBeam (mesh_t *mesh){

	glIndex_t	*indices;
	glVertex_t	*vertices;
	odMat3		axis;
	float		length;
	int			i;

	tr.pc.dynamicBeams++;
	tr.pc.dynamicIndices += 6;
	tr.pc.dynamicVertices += 4;

	// Check for overflow
	RB_CheckOverflow(6, 4, MAX_INDICES, MAX_VERTICES);

	// Compute orientation vectors
	axis[0] = backEnd.viewParms.origin - backEnd.entity->e.origin;
	axis[1] = backEnd.entity->e.beamEnd - backEnd.entity->e.origin;

	axis[2].Cross(axis[0], axis[1]);
	axis[2].NormalizeFast();

	// Compute normal
	axis[0].Cross(axis[1], axis[2]);
	axis[0].NormalizeFast();

	// Scale by half-width
	axis[2] *= backEnd.entity->e.beamWidth * 0.5f;

	// Compute segment length
	if (backEnd.entity->e.beamLength)
		length = axis[1].LengthFast() / backEnd.entity->e.beamLength;
	else
		length = axis[1].LengthFast() / 100.0f;

	// Set up indices
	indices = backEnd.indices + backEnd.numIndices;

	for (i = 0; i < 2; i++){
		indices[i*3+0] = backEnd.numVertices;
		indices[i*3+1] = backEnd.numVertices + i + 1;
		indices[i*3+2] = backEnd.numVertices + i + 2;
	}

	backEnd.numIndices += 6;

	// Set up vertices
	vertices = backEnd.vertices + backEnd.numVertices;

	vertices[0].xyz[0] = backEnd.entity->e.origin[0] + axis[2][0];
	vertices[0].xyz[1] = backEnd.entity->e.origin[1] + axis[2][1];
	vertices[0].xyz[2] = backEnd.entity->e.origin[2] + axis[2][2];
	vertices[1].xyz[0] = backEnd.entity->e.beamEnd[0] + axis[2][0];
	vertices[1].xyz[1] = backEnd.entity->e.beamEnd[1] + axis[2][1];
	vertices[1].xyz[2] = backEnd.entity->e.beamEnd[2] + axis[2][2];
	vertices[2].xyz[0] = backEnd.entity->e.beamEnd[0] - axis[2][0];
	vertices[2].xyz[1] = backEnd.entity->e.beamEnd[1] - axis[2][1];
	vertices[2].xyz[2] = backEnd.entity->e.beamEnd[2] - axis[2][2];
	vertices[3].xyz[0] = backEnd.entity->e.origin[0] - axis[2][0];
	vertices[3].xyz[1] = backEnd.entity->e.origin[1] - axis[2][1];
	vertices[3].xyz[2] = backEnd.entity->e.origin[2] - axis[2][2];

	vertices[0].st[0] = 0.0f;
	vertices[0].st[1] = 0.0f;
	vertices[1].st[0] = length;
	vertices[1].st[1] = 0.0f;
	vertices[2].st[0] = length;
	vertices[2].st[1] = 1.0f;
	vertices[3].st[0] = 0.0f;
	vertices[3].st[1] = 1.0f;

	for (i = 0; i < 4; i++){
		vertices[i].normal[0] = axis[0][0];
		vertices[i].normal[1] = axis[0][1];
		vertices[i].normal[2] = axis[0][2];
		vertices[i].color[0] = 255;
		vertices[i].color[1] = 255;
		vertices[i].color[2] = 255;
		vertices[i].color[3] = 255;
	}

	backEnd.numVertices += 4;
}

/*
 ==================
 RB_BatchParticle
 ==================
*/
static void RB_BatchParticle (mesh_t *mesh){

	particle_t	*particle = (particle_t *)mesh;
	glIndex_t	*indices;
	glVertex_t	*vertices;
	odMat3		axis;
	float		s, c;
	int			i;

	tr.pc.dynamicParticles++;
	tr.pc.dynamicIndices += 6;
	tr.pc.dynamicVertices += 4;

	// Check for overflow
	RB_CheckOverflow(6, 4, MAX_INDICES, MAX_VERTICES);

	// Set up indices
	indices = backEnd.indices + backEnd.numIndices;

	for (i = 0; i < 2; i++){
		indices[i*3+0] = backEnd.numVertices;
		indices[i*3+1] = backEnd.numVertices + i + 1;
		indices[i*3+2] = backEnd.numVertices + i + 2;
	}

	backEnd.numIndices += 6;

	// Set up vertices
	vertices = backEnd.vertices + backEnd.numVertices;

	if (particle->p.type == RP_AIMED){
		// Compute orientation vectors
		axis[0] = backEnd.viewParms.origin - backEnd.entity->e.origin;
		axis[1] = particle->p.direction;

		axis[2].Cross(axis[0], axis[1]);
		axis[2].NormalizeFast();

		// Compute normal
		axis[0].Cross(axis[1], axis[2]);
		axis[0].NormalizeFast();

		// Scale by length
		axis[1] *= particle->p.length;

		// Scale by radius
		axis[2] *= particle->p.radius;

		vertices[0].xyz[0] = particle->p.origin[0] + axis[2][0];
		vertices[0].xyz[1] = particle->p.origin[1] + axis[2][1];
		vertices[0].xyz[2] = particle->p.origin[2] + axis[2][2];
		vertices[1].xyz[0] = particle->p.origin[0] + axis[1][0] + axis[2][0];
		vertices[1].xyz[1] = particle->p.origin[1] + axis[1][1] + axis[2][1];
		vertices[1].xyz[2] = particle->p.origin[2] + axis[1][2] + axis[2][2];
		vertices[2].xyz[0] = particle->p.origin[0] + axis[1][0] - axis[2][0];
		vertices[2].xyz[1] = particle->p.origin[1] + axis[1][1] - axis[2][1];
		vertices[2].xyz[2] = particle->p.origin[2] + axis[1][2] - axis[2][2];
		vertices[3].xyz[0] = particle->p.origin[0] - axis[2][0];
		vertices[3].xyz[1] = particle->p.origin[1] - axis[2][1];
		vertices[3].xyz[2] = particle->p.origin[2] - axis[2][2];
	}
	else {
		// Compute axes
		if (particle->p.type == RP_ORIENTED){
			axis[0] = particle->p.axis[0];

			if (particle->p.rotation){
				M_SinCos(DEG2RAD(particle->p.rotation), &s, &c);

				s *= particle->p.radius;
				c *= particle->p.radius;

				axis[1] = particle->p.axis[1] * c - particle->p.axis[2] * s;
				axis[2] = particle->p.axis[2] * c + particle->p.axis[1] * s;
			}
			else {
				axis[1] = particle->p.axis[1] * particle->p.radius;
				axis[2] = particle->p.axis[2] * particle->p.radius;
			}
		}
		else {
			axis[0] = -backEnd.viewParms.axis[0];

			if (particle->p.rotation){
				M_SinCos(DEG2RAD(particle->p.rotation), &s, &c);

				s *= particle->p.radius;
				c *= particle->p.radius;

				axis[1] = backEnd.viewParms.axis[1] * c - backEnd.viewParms.axis[2] * s;
				axis[2] = backEnd.viewParms.axis[2] * c + backEnd.viewParms.axis[1] * s;
			}
			else {
				axis[1] = backEnd.viewParms.axis[1] * particle->p.radius;
				axis[2] = backEnd.viewParms.axis[2] * particle->p.radius;
			}
		}

		vertices[0].xyz[0] = particle->p.origin[0] + axis[1][0] + axis[2][0];
		vertices[0].xyz[1] = particle->p.origin[1] + axis[1][1] + axis[2][1];
		vertices[0].xyz[2] = particle->p.origin[2] + axis[1][2] + axis[2][2];
		vertices[1].xyz[0] = particle->p.origin[0] - axis[1][0] + axis[2][0];
		vertices[1].xyz[1] = particle->p.origin[1] - axis[1][1] + axis[2][1];
		vertices[1].xyz[2] = particle->p.origin[2] - axis[1][2] + axis[2][2];
		vertices[2].xyz[0] = particle->p.origin[0] - axis[1][0] - axis[2][0];
		vertices[2].xyz[1] = particle->p.origin[1] - axis[1][1] - axis[2][1];
		vertices[2].xyz[2] = particle->p.origin[2] - axis[1][2] - axis[2][2];
		vertices[3].xyz[0] = particle->p.origin[0] + axis[1][0] - axis[2][0];
		vertices[3].xyz[1] = particle->p.origin[1] + axis[1][1] - axis[2][1];
		vertices[3].xyz[2] = particle->p.origin[2] + axis[1][2] - axis[2][2];
	}

	vertices[0].st[0] = 0.0f;
	vertices[0].st[1] = 0.0f;
	vertices[1].st[0] = 1.0f;
	vertices[1].st[1] = 0.0f;
	vertices[2].st[0] = 1.0f;
	vertices[2].st[1] = 1.0f;
	vertices[3].st[0] = 0.0f;
	vertices[3].st[1] = 1.0f;

	for (i = 0; i < 4; i++){
		vertices[i].normal[0] = axis[0][0];
		vertices[i].normal[1] = axis[0][1];
		vertices[i].normal[2] = axis[0][2];
		vertices[i].color[0] = particle->p.modulate[0];
		vertices[i].color[1] = particle->p.modulate[1];
		vertices[i].color[2] = particle->p.modulate[2];
		vertices[i].color[3] = particle->p.modulate[3];
	}

	backEnd.numVertices += 4;
}

/*
 ==================
 RB_BatchDecal
 ==================
*/
static void RB_BatchDecal (mesh_t *mesh){

	decal_t			*decal = (decal_t *)mesh;
	decalInfo_t		*decalInfo = &backEnd.material->decalInfo;
	mdlSurface_t	*surface;
	glIndex_t		*index;
	glVertex_t		*vertex;
	glIndex_t		*indices;
	glVertex_t		*vertices;
	odVec4			color;
	float			time;
	byte			vertexColor[4];
	int				i;

	tr.pc.dynamicDecals++;
	tr.pc.dynamicIndices += decal->numIndices;
	tr.pc.dynamicVertices += decal->numVertices;

	// Check for overflow
	RB_CheckOverflow(decal->numIndices, decal->numVertices, MAX_INDICES, MAX_VERTICES);

	// Compute vertex color
	time = backEnd.time - decal->startTime;

	if (time <= decalInfo->stayTime)
		color = decalInfo->startRGBA;
	else
		color.Lerp(decalInfo->startRGBA, decalInfo->endRGBA, (time - decalInfo->stayTime) * (1.0f / decalInfo->fadeTime));

	color.Clamp(0.0f, 1.0f);

	*(dword *)vertexColor = PackColor(color);

	// Set up indices
	indices = backEnd.indices + backEnd.numIndices;

	for (i = 0, index = decal->indices; i < decal->numIndices; i += 3, index += 3){
		indices[i+0] = backEnd.numVertices + index[0];
		indices[i+1] = backEnd.numVertices + index[1];
		indices[i+2] = backEnd.numVertices + index[2];
	}

	backEnd.numIndices += decal->numIndices;

	// Set up vertices
	vertices = backEnd.vertices + backEnd.numVertices;

	if (decal->baseModel && decal->parentEntity->instance){
		surface = decal->parentEntity->instance->surfaces + decal->baseSurface;

		for (i = 0, vertex = decal->vertices; i < decal->numVertices; i++, vertex++){
			vertices[i].xyz[0] = surface->vertices[decal->baseVertices[i]].xyz[0];
			vertices[i].xyz[1] = surface->vertices[decal->baseVertices[i]].xyz[1];
			vertices[i].xyz[2] = surface->vertices[decal->baseVertices[i]].xyz[2];
			vertices[i].normal[0] = surface->facePlanes[decal->baseTriangles[i]].normal[0];
			vertices[i].normal[1] = surface->facePlanes[decal->baseTriangles[i]].normal[1];
			vertices[i].normal[2] = surface->facePlanes[decal->baseTriangles[i]].normal[2];
			vertices[i].st[0] = vertex->st[0];
			vertices[i].st[1] = vertex->st[1];
			vertices[i].color[0] = vertexColor[0];
			vertices[i].color[1] = vertexColor[1];
			vertices[i].color[2] = vertexColor[2];
			vertices[i].color[3] = vertexColor[3];
		}
	}
	else {
		for (i = 0, vertex = decal->vertices; i < decal->numVertices; i++, vertex++){
			vertices[i].xyz[0] = vertex->xyz[0];
			vertices[i].xyz[1] = vertex->xyz[1];
			vertices[i].xyz[2] = vertex->xyz[2];
			vertices[i].normal[0] = vertex->normal[0];
			vertices[i].normal[1] = vertex->normal[1];
			vertices[i].normal[2] = vertex->normal[2];
			vertices[i].st[0] = vertex->st[0];
			vertices[i].st[1] = vertex->st[1];
			vertices[i].color[0] = vertexColor[0];
			vertices[i].color[1] = vertexColor[1];
			vertices[i].color[2] = vertexColor[2];
			vertices[i].color[3] = vertexColor[3];
		}
	}

	backEnd.numVertices += decal->numVertices;
}

/*
 ==================
 RB_BatchMesh
 ==================
*/
void RB_BatchMesh (meshType_t type, mesh_t *mesh){

	if (r_skipBatching->integerValue || backEnd.material->deform == DFRM_FLARE){
		RB_EndBatch();

		RB_BeginBatch(backEnd.material, backEnd.entity, backEnd.shadowVolume, backEnd.shadowCaps);
	}

	switch (type){
	case MESH_SURFACE:
		RB_BatchSurface(mesh);
		break;
	case MESH_MODEL:
		RB_BatchModel(mesh);
		break;
	case MESH_FOLIAGE:
		RB_BatchFoliage(mesh);
		break;
	case MESH_SPRITE:
		RB_BatchSprite(mesh);
		break;
	case MESH_BEAM:
		RB_BatchBeam(mesh);
		break;
	case MESH_PARTICLE:
		RB_BatchParticle(mesh);
		break;
	case MESH_DECAL:
		RB_BatchDecal(mesh);
		break;
	default:
		Com_Error(false, "RB_BatchMesh: bad type (%i)", type);
	}
}


/*
 ==============================================================================

 SHADOW RENDERING

 ==============================================================================
*/


/*
 ==================
 RB_BatchSurfaceShadow
 ==================
*/
static void RB_BatchSurfaceShadow (mesh_t *mesh){

	bool				facing[(MAX_INDICES / 3) + 1];
	glIndex_t			vertexRemap[MAX_VERTICES];
	surface_t			*surface = (surface_t *)mesh;
	glIndex_t			*index;
	glVertex_t			*vertex;
	int					*neighbor;
	odPlane				*facePlane;
	glIndex_t			*indices;
	glShadowVertex_t	*vertices;
	int					numTriangles = surface->numIndices / 3;
	glIndex_t			v0, v1, v2;
	int					i;

	// Check for overflow
	if (backEnd.shadowCaps)
		RB_CheckOverflow(surface->numIndices * 8, surface->numVertices * 2, MAX_SHADOW_INDICES, MAX_SHADOW_VERTICES);
	else
		RB_CheckOverflow(surface->numIndices * 6, surface->numVertices * 2, MAX_SHADOW_INDICES, MAX_SHADOW_VERTICES);

	// Check for fast path
	if (!backEnd.debugTools && surface->shadowBuffer){
		// Set up vertex buffer
		backEnd.vertexBuffer = surface->shadowBuffer;

		// Set up vertex remapping table
		for (i = 0; i < surface->numVertices; i++)
			vertexRemap[i] = surface->shadowIndex + (i << 1);
	}
	else {
		// Set up vertices and vertex remapping table
		vertices = backEnd.shadowVertices + backEnd.numVertices;

		for (i = 0, vertex = surface->vertices; i < surface->numVertices; i++, vertex++){
			vertices[i*2+0].xyzw[0] = vertex->xyz[0];
			vertices[i*2+0].xyzw[1] = vertex->xyz[1];
			vertices[i*2+0].xyzw[2] = vertex->xyz[2];
			vertices[i*2+0].xyzw[3] = 1.0f;

			vertices[i*2+1].xyzw[0] = vertex->xyz[0];
			vertices[i*2+1].xyzw[1] = vertex->xyz[1];
			vertices[i*2+1].xyzw[2] = vertex->xyz[2];
			vertices[i*2+1].xyzw[3] = 0.0f;

			vertexRemap[i] = backEnd.numVertices + (i << 1);
		}
	}

	backEnd.numVertices += surface->numVertices * 2;

	// Find front facing triangles
	if (surface->plane){
		for (i = 0; i < numTriangles; i++)
			facing[i] = true;
	}
	else {
		for (i = 0, facePlane = surface->facePlanes; i < numTriangles; i++, facePlane++){
			if (facePlane->Distance(backEnd.localParms.lightOrigin) > 0.0f)
				facing[i] = true;
			else
				facing[i] = false;
		}
	}

	facing[numTriangles] = false;

	// Set up indices
	indices = backEnd.shadowIndices + backEnd.numIndices;

	for (i = 0, index = surface->indices, neighbor = surface->neighbors; i < numTriangles; i++, index += 3, neighbor += 3){
		if (!facing[i])
			continue;

		v0 = vertexRemap[index[0]];
		v1 = vertexRemap[index[1]];
		v2 = vertexRemap[index[2]];

		// If needed, set up front and back caps
		if (backEnd.shadowCaps){
			indices[0] = v0;
			indices[1] = v1;
			indices[2] = v2;
			indices[3] = v2 + 1;
			indices[4] = v1 + 1;
			indices[5] = v0 + 1;

			indices += 6;
		}

		// If needed, set up silhouette edges
		if (!facing[neighbor[0]]){
			indices[0] = v1;
			indices[1] = v0;
			indices[2] = v0 + 1;
			indices[3] = v1;
			indices[4] = v0 + 1;
			indices[5] = v1 + 1;

			indices += 6;
		}

		if (!facing[neighbor[1]]){
			indices[0] = v2;
			indices[1] = v1;
			indices[2] = v1 + 1;
			indices[3] = v2;
			indices[4] = v1 + 1;
			indices[5] = v2 + 1;

			indices += 6;
		}

		if (!facing[neighbor[2]]){
			indices[0] = v0;
			indices[1] = v2;
			indices[2] = v2 + 1;
			indices[3] = v0;
			indices[4] = v2 + 1;
			indices[5] = v0 + 1;

			indices += 6;
		}
	}

	backEnd.numIndices += indices - (backEnd.shadowIndices + backEnd.numIndices);
}

/*
 ==================
 RB_BatchModelShadow
 ==================
*/
static void RB_BatchModelShadow (mesh_t *mesh){

	bool				facing[(MAX_INDICES / 3) + 1];
	glIndex_t			vertexRemap[MAX_VERTICES];
	mdlSurface_t		*surface = (mdlSurface_t *)mesh;
	glIndex_t			*index;
	glVertex_t			*vertex;
	int					*neighbor;
	odPlane				*facePlane;
	glIndex_t			*indices;
	glShadowVertex_t	*vertices;
	int					numTriangles = surface->numIndices / 3;
	glIndex_t			v0, v1, v2;
	int					i;

	// Check for overflow
	if (backEnd.shadowCaps)
		RB_CheckOverflow(surface->numIndices * 8, surface->numVertices * 2, MAX_SHADOW_INDICES, MAX_SHADOW_VERTICES);
	else
		RB_CheckOverflow(surface->numIndices * 6, surface->numVertices * 2, MAX_SHADOW_INDICES, MAX_SHADOW_VERTICES);

	// Check for fast path
	if (!backEnd.debugTools && surface->shadowBuffer){
		// Set up vertex buffer
		backEnd.vertexBuffer = surface->shadowBuffer;

		// Set up vertex remapping table
		for (i = 0; i < surface->numVertices; i++)
			vertexRemap[i] = surface->shadowIndex + (i << 1);
	}
	else {
		// Set up vertices and vertex remapping table
		vertices = backEnd.shadowVertices + backEnd.numVertices;

		for (i = 0, vertex = surface->vertices; i < surface->numVertices; i++, vertex++){
			vertices[i*2+0].xyzw[0] = vertex->xyz[0];
			vertices[i*2+0].xyzw[1] = vertex->xyz[1];
			vertices[i*2+0].xyzw[2] = vertex->xyz[2];
			vertices[i*2+0].xyzw[3] = 1.0f;

			vertices[i*2+1].xyzw[0] = vertex->xyz[0];
			vertices[i*2+1].xyzw[1] = vertex->xyz[1];
			vertices[i*2+1].xyzw[2] = vertex->xyz[2];
			vertices[i*2+1].xyzw[3] = 0.0f;

			vertexRemap[i] = backEnd.numVertices + (i << 1);
		}
	}

	backEnd.numVertices += surface->numVertices * 2;

	// Find front facing triangles
	for (i = 0, facePlane = surface->facePlanes; i < numTriangles; i++, facePlane++){
		if (facePlane->Distance(backEnd.localParms.lightOrigin) > 0.0f)
			facing[i] = true;
		else
			facing[i] = false;
	}

	facing[numTriangles] = false;

	// Set up indices
	indices = backEnd.shadowIndices + backEnd.numIndices;

	for (i = 0, index = surface->indices, neighbor = surface->neighbors; i < numTriangles; i++, index += 3, neighbor += 3){
		if (!facing[i])
			continue;

		v0 = vertexRemap[index[0]];
		v1 = vertexRemap[index[1]];
		v2 = vertexRemap[index[2]];

		// If needed, set up front and back caps
		if (backEnd.shadowCaps){
			indices[0] = v0;
			indices[1] = v1;
			indices[2] = v2;
			indices[3] = v2 + 1;
			indices[4] = v1 + 1;
			indices[5] = v0 + 1;

			indices += 6;
		}

		// If needed, set up silhouette edges
		if (!facing[neighbor[0]]){
			indices[0] = v1;
			indices[1] = v0;
			indices[2] = v0 + 1;
			indices[3] = v1;
			indices[4] = v0 + 1;
			indices[5] = v1 + 1;

			indices += 6;
		}

		if (!facing[neighbor[1]]){
			indices[0] = v2;
			indices[1] = v1;
			indices[2] = v1 + 1;
			indices[3] = v2;
			indices[4] = v1 + 1;
			indices[5] = v2 + 1;

			indices += 6;
		}

		if (!facing[neighbor[2]]){
			indices[0] = v0;
			indices[1] = v2;
			indices[2] = v2 + 1;
			indices[3] = v0;
			indices[4] = v2 + 1;
			indices[5] = v0 + 1;

			indices += 6;
		}
	}

	backEnd.numIndices += indices - (backEnd.shadowIndices + backEnd.numIndices);
}

/*
 ==================
 RB_BatchMeshShadow
 ==================
*/
void RB_BatchMeshShadow (meshType_t type, mesh_t *mesh){

	if (r_skipBatching->integerValue || backEnd.material->deform == DFRM_FLARE){
		RB_EndBatch();

		RB_BeginBatch(backEnd.material, backEnd.entity, backEnd.shadowVolume, backEnd.shadowCaps);
	}

	switch (type){
	case MESH_SURFACE:
		RB_BatchSurfaceShadow(mesh);
		break;
	case MESH_MODEL:
		RB_BatchModelShadow(mesh);
		break;
	default:
		Com_Error(false, "RB_BatchMeshShadow: bad type (%i)", type);
	}
}
